Librerías

library(dplyr)
library(data.table)
library(lubridate)
library(scales)
library(ggplot2)
library(downloader)

Funciones

isValidEmail <- function(x) {
        grepl("\\<[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}\\>", as.character(x), ignore.case=TRUE)
}
leerArchivo <- function(nombre) {
        temp <- fread(nombre, sep = "~", quote = "", encoding = "UTF-8", stringsAsFactors = TRUE, fill = TRUE, data.table = TRUE)
        colnames(temp) <- toupper(colnames(temp))
        if (anyDuplicated(colnames(temp))) {
             colnames(temp) <- make.unique(colnames(temp) )   
        }
        temp
}

Objetivo

El objetivo de este script es el filtrado de los datos de origen de e-Preselec ESP para su carga en Cornerstone para el ámbito Holding.

En la subcarpeta datos_originales/ están todos los archivos fuente obtenidos de e-Preselec ESP

ptm <- proc.time()
files <- list.files(path="datos_originales", pattern="*.csv", full.names=T, recursive=FALSE)
print (files)
 [1] "datos_originales/01-Candidatos_Datos_Personales.csv"                       
 [2] "datos_originales/02-Candidatos_Formacion_Parte1.csv"                       
 [3] "datos_originales/03-Candidatos_Formacion_Parte2.csv"                       
 [4] "datos_originales/06-Candidatos_Idiomas.csv"                                
 [5] "datos_originales/07-Candidatos_Idiomas_Titulaciones.csv"                   
 [6] "datos_originales/08-Candidatos_Idiomas_Detalles.csv"                       
 [7] "datos_originales/09-Candidatos_Informatica_parte1.csv"                     
 [8] "datos_originales/10-Candidatos_Informatica_parte2.csv"                     
 [9] "datos_originales/11-Candidatos_Informatica_parte3.csv"                     
[10] "datos_originales/12-Candidatos_Informatica_parte4.csv"                     
[11] "datos_originales/13-Candidatos_Experiencia_Empresas_Parte1.csv"            
[12] "datos_originales/14-Candidatos_Experiencia_Empresas_Parte2.csv"            
[13] "datos_originales/15-Candidatos_Experiencia_Profesional.csv"                
[14] "datos_originales/16-Candidatos_Otros_Datos.csv"                            
[15] "datos_originales/17-Candidatos_Otros_Datos_Documentos.csv"                 
[16] "datos_originales/18-Candidatos_Agenda.csv"                                 
[17] "datos_originales/19-Candidatos_Agenda_Citaciones.csv"                      
[18] "datos_originales/19-Procesos_Masivos.csv"                                  
[19] "datos_originales/20-Candidatos_Agenda_VEntrevista.csv"                     
[20] "datos_originales/20-Procesos_Especialistas.csv"                            
[21] "datos_originales/21-Procesos_Becarios.csv"                                 
[22] "datos_originales/22-Procesos_Puestos.csv"                                  
[23] "datos_originales/23-Procesos_VEntrevista.csv"                              
[24] "datos_originales/24-Procesos_Evaluacion_Candidato_Cp.csv"                  
[25] "datos_originales/25-Procesos_Evaluacion_Candidato_Test.csv"                
[26] "datos_originales/26-Procesos_Evaluacion_Candidato_Idiomas.csv"             
[27] "datos_originales/27-Procesos_Evaluacion_Candidato_Resultados_Parte1.csv"   
[28] "datos_originales/28-Procesos_Evaluacion_Candidato_Resultados_Parte2.csv"   
[29] "datos_originales/29-Procesos_Evaluacion_Candidato_Evaluacion_BBVA.csv"     
[30] "datos_originales/30-Procesos_Evaluacion_Candidato_Entrevista_Historico.csv"
[31] "datos_originales/31-Procesos_Citaciones_Eventos.csv"                       
[32] "datos_originales/32-Procesos_Citaciones_Fases.csv"                         
[33] "datos_originales/33-Procesos_Candidatos_Inscritos.csv"                     
[34] "datos_originales/34-Procesos_Becarios_Progreso.csv"                        
[35] "datos_originales/35-Procesos_Especialistas_Progreso.csv"                   
[36] "datos_originales/36-Gestion_Bolsa_Masivos.csv"                             
[37] "datos_originales/37-Ofertas.csv"                                           
[38] "datos_originales/38-Ofertas_Plantillas.csv"                                
[39] "datos_originales/39-Ofertas_Preguntas.csv"                                 
[40] "datos_originales/40-Ofertas_Respuestas.csv"                                
[41] "datos_originales/41-Candidatos_En_Oferta.csv"                              
[42] "datos_originales/42-Becas_Centros.csv"                                     
[43] "datos_originales/43-Becas_Centros_Contactos.csv"                           
[44] "datos_originales/44-Becas_Centros_Convenios.csv"                           
[45] "datos_originales/45-Candidatos_Referenciados.csv"                          
[46] "datos_originales/46-Empresas_Gestores.csv"                                 
[47] "datos_originales/47-Informes_Informe_Personal.csv"                         
[48] "datos_originales/48-Informes_Informe_Banca_Comercial.csv"                  
[49] "datos_originales/49-Informes_Informe_Banca_Comercial_Competencias.csv"     
[50] "datos_originales/50-Informes_Informe_Banca_Comercial_Areas_Adecuacion.csv" 
[51] "datos_originales/51-Candidatos_Comunicados_Parte1.csv"                     
[52] "datos_originales/52-Candidatos_Comunicados_Parte2.csv"                     
[53] "datos_originales/53-Candidatos_Comunicados_Parte3.csv"                     
[54] "datos_originales/54-Comunicados.csv"                                       
rm(files)

En la subcarpeta output/ dejaremos los ficheros de salida ya filtrados y limpiados

Maestro de Candidatos

Leemos el fichero de maestro de candidatos y vamos a ir eliminando candidatos por diferentes criterios * Primero anonimizados, nombre en blanco, email en blanco, emails ficticios

nombre <- "datos_originales/01-Candidatos_Datos_Personales.csv"
candidatos_maestro <- leerArchivo(nombre)

Read 87.3% of 549653 rows
Read 549652 rows and 25 (of 25) columns from 0.143 GB file in 00:00:03
candidatos_maestro <- filter(candidatos_maestro, NOMBRE != "XXXXXX")
candidatos_maestro <- filter(candidatos_maestro, NOMBRE != "")
candidatos_maestro <- filter(candidatos_maestro, EMAIL != "")
candidatos_maestro <- filter(candidatos_maestro, !(EMAIL %like% "@infojobs"))
candidatos_maestro <- filter(candidatos_maestro, !(EMAIL %like% "@infoempleo"))
candidatos_maestro <- filter(candidatos_maestro, !(EMAIL %like% "@vacio.com"))
candidatos_maestro <- filter(candidatos_maestro, !(EMAIL %like% "@nada"))

De los que quedan, vamos a extraer los ID de candidatos de los actualizados en los últimos 24 meses

fecha_desde <- today() - months(24)
fecha_hasta <- today()

Y nos salen

candidatos_historicos <- candidatos_maestro %>%
                        filter(dmy_hms(FECHA_ACTUALIZACION) >= fecha_desde) %>%
                        select(ID_CANDIDATO)
sprintf("Nº de Candidatos: %i", nrow(candidatos_historicos))
[1] "Nº de Candidatos: 92534"

Antes de seguir eliminando candidatos por otros criterios, tenemos que identificar cuáles de ellos están en procesos vivos, no sea que eliminemos candidatos actualizados hace mucho pero que están en un proceso vivo

Maestro de Procesos

Tenemos que leer el fichero maestro de procesos de selección (que son tres: especialistas, becarios y masivos) abiertos para identificar qué procesos están “vivos”"

Estos serán los criterios de filtrado a definir por el usuario antes de ejecutar la carga

# Estos filtros no son necesarios si migramos toda España
#gestores_filtro <- c("Naiara Martínez", "Elena Herbosa", "Sara Abad", "Susana Gutierrez", "")
# masivos_filtro <- c("12841","12842")
# becarios_filtro <- c("Naiara Martínez", "Elena Herbosa", "Sara Abad", "Susana Gutierrez", "")

Nos salen los siguientes procesos activos, entre becarios, masivos y especialistas

Candidatos en procesos Vivos

Tenemos que extraer los ID de los candidatos asociados a los procesos vivos, independientemente del estado del candidato

Nos salen

Juntando los candidatos en procesos vivos, y los candidatos actualizados recientemente, tenemos el siguiente número de candidatos

Vamos a eliminar por tanto todos los candidatos que no estén en esa lista y a ver cuántos quedan

Si no coinciden los números es porque hay candidatos que hemos eliminado previamente por alguno de los otros criterios.

Filtros Adicionales en Candidatos

Tampoco cargamos candidatos cuyo CV no está activo

candidatos_maestro <- filter(candidatos_maestro, CV_ACTIVO == "Si")
print(nrow(candidatos_maestro))
[1] 92638

tratamos de arreglar emails inválidos?

emails_incorectos <- filter(candidatos_maestro,!isValidEmail(EMAIL))
print(emails_incorectos["EMAIL"])

eliminamos los emails inválidos

candidatos_maestro <- filter(candidatos_maestro, isValidEmail(EMAIL))

De los candidatos duplicados trataremos de cargar únicamente una ocurrencia. Estos son algunos de los repetidos

emails_repetidos <- candidatos_maestro %>% group_by(EMAIL) %>%
        summarise(count = n()) %>% 
        arrange(desc(count)) %>% filter(count > 1)
print(emails_repetidos)

Eliminamos candidatos duplicados, dejando únicamente duplicados si están asociados a procesos

#Obtenemos una lista de emails y candidatos que no están en procesos vivos
candidatos_a_unificar <- candidatos_maestro %>%
                                filter(EMAIL %in% emails_repetidos$EMAIL) %>%
                                select(ID_CANDIDATO) %>%
                                filter(!(ID_CANDIDATO %in% candidatos_procesos_vivos$ID_CANDIDATO))
candidatos_mantener <- candidatos_maestro %>%
                        filter(ID_CANDIDATO %in% candidatos_a_unificar$ID_CANDIDATO) %>%
                        arrange(desc(dmy_hms(FECHA_ACTUALIZACION))) %>%
                        distinct(EMAIL, .keep_all=TRUE ) %>%
                        select(ID_CANDIDATO)
candidatos_eliminar <- candidatos_maestro %>%
                        filter(ID_CANDIDATO %in% candidatos_a_unificar$ID_CANDIDATO) %>%
                        filter(!(ID_CANDIDATO %in% candidatos_mantener$ID_CANDIDATO)) %>%
                        select(ID_CANDIDATO)
#Ahora eliminamos estos candidatos del maestro
candidatos_maestro <- filter(candidatos_maestro, !(ID_CANDIDATO %in% candidatos_eliminar$ID_CANDIDATO))
rm(candidatos_a_unificar)
rm(candidatos_mantener)
rm(candidatos_eliminar)

Comprobamos si quedan duplicados

emails_repetidos <- candidatos_maestro %>% group_by(EMAIL) %>%
        summarise(count = n()) %>% 
        arrange(desc(count)) %>% filter(count > 1)
print(emails_repetidos)

Si quedan duplicados puede ser por dos motivos: 1. El candidato está inscrito a más de una oferta de empleo con el mismo email pero diferente ID de candidato 2. El candidato está inscrito a una oferta de empleo (o más) con un email e ID de Candidato y aparece en el histórico con el mismo email pero otro ID de Candidato.

En este caso eliminamos los candidatos que cumplen la casuística número 2, dejando únicamente su ID de candidato que está asociado a alguna oferta de empleo.

#Obtenemos una lista de emails y candidatos que no están en procesos vivos
candidatos_a_unificar <- candidatos_maestro %>%
                                filter(EMAIL %in% emails_repetidos$EMAIL) %>%
                                select(ID_CANDIDATO) %>%
                                filter(!(ID_CANDIDATO %in% candidatos_procesos_vivos$ID_CANDIDATO))
#y nos los cargamos
candidatos_maestro <- filter(candidatos_maestro, !(ID_CANDIDATO %in% candidatos_a_unificar$ID_CANDIDATO))
rm(candidatos_a_unificar)

Comprobamos si quedan duplicados

emails_repetidos <- candidatos_maestro %>% group_by(EMAIL) %>%
        summarise(count = n()) %>% 
        arrange(desc(count)) %>% filter(count > 1)
print(emails_repetidos)
rm(emails_repetidos)

Si en este punto quedan emails repetidos, tenemos un problema ya que se trata de personas que se han apuntado a dos procesos vigentes, con dos IDs de candidato distinto pero idéntico email. En Cornerstone sólo se puede cargar uno de los dos puesto que la clave principal es el email, el usuario de acceso a Cornerstone será el email y además será uno de los medios de contacto con el candidato por lo que no lo podemos cambiar.

PENSAR QUE HACER

A ver cuántos candidatos nos quedan:

fechas <- select(candidatos_maestro,ID_CANDIDATO,FECHA_ACTUALIZACION)
fechas$FECHA_ACTUALIZACION <- as.POSIXct(date(dmy_hms(fechas$FECHA_ACTUALIZACION)))
ggplot(fechas, aes(FECHA_ACTUALIZACION)) + 
        geom_histogram(aes(fill=..count..)) +
        labs(title="Histograma de Candidatos por fecha de última actualización") +
        labs(x="Fecha", y="Número de Actualizaciones") + 
        scale_x_datetime(breaks = date_breaks("12 months"),
                         labels = date_format("%Y-%b")
                          )

fechas <- select(candidatos_maestro,ID_CANDIDATO,FECHA_ALTA)
fechas$FECHA_ALTA <- as.POSIXct(date(dmy_hms(fechas$FECHA_ALTA)))
ggplot(fechas, aes(FECHA_ALTA)) + 
        geom_histogram(aes(fill=..count..)) +
        labs(title="Histograma de Candidatos por fecha de alta") +
        labs(x="Fecha", y="Número de Altas") + 
        scale_x_datetime(breaks = date_breaks("24 months"),
                         labels = date_format("%Y-%b")
        )

Filtrado del resto de ficheros

Ahora tenemos por un lado el listado de candidatos a migrar y por otro el listado de procesos (y ofertas) a migrar. Ahora hay que procesar todos y cada uno de los ficheros CSV de entrada y filtrarlos para dejar únicamente los candidatos, procesos, o la combinación de ámbos.

# Por el momento, en los ficheros de procesos dejamos solo la información de los procesos vivos, eliminando todo lo demás
# Otra opción sería dejar la información de los candidatos históricos también pero modificando el ID de proceso de todos ellos a uno "ficticio" a mapear con el Dummy de cornerstone. Como el requerimiento es cargar candidatos históricos únicamente, (no la información asociada a sus 1 o varios procesos de selección históricos) de momento lo dejamos así.
fileNames <- list.files(path = "datos_originales", pattern="*.csv", full.names=T, recursive=FALSE)
fileNamesOut <- file.path("temp",list.files(path = "datos_originales", pattern="*.csv", recursive=FALSE))
for(x in 1:length(fileNames)) {
        t<-leerArchivo(fileNames[x])
        if ("ID_PROCESO" %in% colnames(t)) {
                t <- t %>% filter(ID_PROCESO %in% procesos_activos$ID_PROCESO)
        }
        else {
                if ("ID_CANDIDATO" %in% colnames(t)) {
                        t <- t %>% filter(ID_CANDIDATO %in% candidatos_maestro$ID_CANDIDATO)
                }
                else {
                        if ("REFERENCIA" %in% colnames(t)) {
                                t <- t %>% filter(REFERENCIA %in% ofertas_activas$REFERENCIA)
                        }
                        else {
                                t <- t %>% filter("REFERENCIA OFERTA" %in% ofertas_activas$REFERENCIA)
                        }
                                
                }
        }
        print(fileNames[x])
        print(nrow(t))
        fwrite(t,fileNamesOut[x], sep = "~")
        rm(t)
        gc()
}

Read 89.1% of 549653 rows
Read 549652 rows and 25 (of 25) columns from 0.143 GB file in 00:00:03
[1] "datos_originales/01-Candidatos_Datos_Personales.csv"
[1] 88327

Read 66.0% of 1000000 rows
Read 999999 rows and 35 (of 35) columns from 0.245 GB file in 00:00:03
[1] "datos_originales/02-Candidatos_Formacion_Parte1.csv"
[1] 59885
[1] "datos_originales/03-Candidatos_Formacion_Parte2.csv"
[1] 104437
[1] "datos_originales/06-Candidatos_Idiomas.csv"
[1] 130092
[1] "datos_originales/07-Candidatos_Idiomas_Titulaciones.csv"
[1] 27127
[1] "datos_originales/08-Candidatos_Idiomas_Detalles.csv"
[1] 17332
[1] "datos_originales/09-Candidatos_Informatica_parte1.csv"
[1] 102162
[1] "datos_originales/10-Candidatos_Informatica_parte2.csv"
[1] 96313
[1] "datos_originales/11-Candidatos_Informatica_parte3.csv"
[1] 123090
[1] "datos_originales/12-Candidatos_Informatica_parte4.csv"
[1] 124614

Read 83.2% of 733422 rows
Read 733422 rows and 11 (of 11) columns from 0.255 GB file in 00:00:03
[1] "datos_originales/13-Candidatos_Experiencia_Empresas_Parte1.csv"
[1] 150360
[1] "datos_originales/14-Candidatos_Experiencia_Empresas_Parte2.csv"
[1] 8073
[1] "datos_originales/15-Candidatos_Experiencia_Profesional.csv"
[1] 8586
[1] "datos_originales/16-Candidatos_Otros_Datos.csv"
[1] 88327
[1] "datos_originales/17-Candidatos_Otros_Datos_Documentos.csv"
[1] 59212
[1] "datos_originales/18-Candidatos_Agenda.csv"
[1] 1589
[1] "datos_originales/19-Candidatos_Agenda_Citaciones.csv"
[1] 3128
[1] "datos_originales/19-Procesos_Masivos.csv"
[1] 34
[1] "datos_originales/20-Candidatos_Agenda_VEntrevista.csv"
[1] 0
[1] "datos_originales/20-Procesos_Especialistas.csv"
[1] 90
Bumped column 27 to type character on data row 314, field contains 'Recibidos cv´s de la Licenciatura de Documentación de la Un. Carlos III'. Coercing previously read values in this column from logical, integer or numeric back to character which may not be lossless; e.g., if '00' and '000' occurred before they will now be just '0', and there may be inconsistencies with treatment of ',,' and ',NA,' too (if they occurred in this column before the bump). If this matters please rerun and set 'colClasses' to 'character' for this column. Please note that column type detection uses a sample of 1,000 rows (100 rows at 10 points) so hopefully this message should be very rare. If reporting to datatable-help, please rerun and include the output from verbose=TRUE.
[1] "datos_originales/21-Procesos_Becarios.csv"
[1] 100
[1] "datos_originales/22-Procesos_Puestos.csv"
[1] 226
[1] "datos_originales/23-Procesos_VEntrevista.csv"
[1] 0
[1] "datos_originales/24-Procesos_Evaluacion_Candidato_Cp.csv"
[1] 2638
[1] "datos_originales/25-Procesos_Evaluacion_Candidato_Test.csv"
[1] 82
[1] "datos_originales/26-Procesos_Evaluacion_Candidato_Idiomas.csv"
[1] 45
[1] "datos_originales/27-Procesos_Evaluacion_Candidato_Resultados_Parte1.csv"
[1] 0
[1] "datos_originales/28-Procesos_Evaluacion_Candidato_Resultados_Parte2.csv"
[1] 4876
[1] "datos_originales/29-Procesos_Evaluacion_Candidato_Evaluacion_BBVA.csv"
[1] 2638
[1] "datos_originales/30-Procesos_Evaluacion_Candidato_Entrevista_Historico.csv"
[1] 50
[1] "datos_originales/31-Procesos_Citaciones_Eventos.csv"
[1] 72
[1] "datos_originales/32-Procesos_Citaciones_Fases.csv"
[1] 217
[1] "datos_originales/33-Procesos_Candidatos_Inscritos.csv"
[1] 3872
Bumped column 9 to type character on data row 32008, field contains '31/01/2011 0:00:00'. Coercing previously read values in this column from logical, integer or numeric back to character which may not be lossless; e.g., if '00' and '000' occurred before they will now be just '0', and there may be inconsistencies with treatment of ',,' and ',NA,' too (if they occurred in this column before the bump). If this matters please rerun and set 'colClasses' to 'character' for this column. Please note that column type detection uses a sample of 1,000 rows (100 rows at 10 points) so hopefully this message should be very rare. If reporting to datatable-help, please rerun and include the output from verbose=TRUE.Bumped column 5 to type character on data row 41145, field contains 'XXX'. Coercing previously read values in this column from logical, integer or numeric back to character which may not be lossless; e.g., if '00' and '000' occurred before they will now be just '0', and there may be inconsistencies with treatment of ',,' and ',NA,' too (if they occurred in this column before the bump). If this matters please rerun and set 'colClasses' to 'character' for this column. Please note that column type detection uses a sample of 1,000 rows (100 rows at 10 points) so hopefully this message should be very rare. If reporting to datatable-help, please rerun and include the output from verbose=TRUE.
[1] "datos_originales/34-Procesos_Becarios_Progreso.csv"
[1] 101
[1] "datos_originales/35-Procesos_Especialistas_Progreso.csv"
[1] 2105
[1] "datos_originales/36-Gestion_Bolsa_Masivos.csv"
[1] 51
[1] "datos_originales/37-Ofertas.csv"
[1] 14
[1] "datos_originales/38-Ofertas_Plantillas.csv"
[1] 0
[1] "datos_originales/39-Ofertas_Preguntas.csv"
[1] 0
[1] "datos_originales/40-Ofertas_Respuestas.csv"
[1] 0
[1] "datos_originales/41-Candidatos_En_Oferta.csv"
[1] 187513
[1] "datos_originales/42-Becas_Centros.csv"
[1] 0
[1] "datos_originales/43-Becas_Centros_Contactos.csv"
[1] 0
[1] "datos_originales/44-Becas_Centros_Convenios.csv"
[1] 0
[1] "datos_originales/45-Candidatos_Referenciados.csv"
[1] 451
[1] "datos_originales/46-Empresas_Gestores.csv"
[1] 0
[1] "datos_originales/47-Informes_Informe_Personal.csv"
[1] 105
[1] "datos_originales/48-Informes_Informe_Banca_Comercial.csv"
[1] 378
[1] "datos_originales/49-Informes_Informe_Banca_Comercial_Competencias.csv"
[1] 0
[1] "datos_originales/50-Informes_Informe_Banca_Comercial_Areas_Adecuacion.csv"
[1] 9
Bumped column 9 to type character on data row 5952, field contains 'Estudiantes de último curso o recién titulados en la Licenciatura o Grado en Informática.'. Coercing previously read values in this column from logical, integer or numeric back to character which may not be lossless; e.g., if '00' and '000' occurred before they will now be just '0', and there may be inconsistencies with treatment of ',,' and ',NA,' too (if they occurred in this column before the bump). If this matters please rerun and set 'colClasses' to 'character' for this column. Please note that column type detection uses a sample of 1,000 rows (100 rows at 10 points) so hopefully this message should be very rare. If reporting to datatable-help, please rerun and include the output from verbose=TRUE.
[1] "datos_originales/51-Candidatos_Comunicados_Parte1.csv"
[1] 28
[1] "datos_originales/52-Candidatos_Comunicados_Parte2.csv"
[1] 1673
[1] "datos_originales/53-Candidatos_Comunicados_Parte3.csv"
[1] 4093
[1] "datos_originales/54-Comunicados.csv"
[1] 0

Limpieza adicional

Con los ficheros ya filtrados, los vamos procesando uno a uno y realizando limpiezas adicionales

fileNames <- list.files(path = "temp", pattern="*.csv", full.names=T, recursive=FALSE)
fileNamesOut <- file.path("output",list.files(path = "temp", pattern="*.csv", recursive=FALSE))
for(x in 1:length(fileNames)) {
        t<-leerArchivo(fileNames[x])
        if (fileNames[x] == "temp/01-Candidatos_Datos_Personales.csv") {
                t$TELEFONO <- gsub("[^0-9]", "", t$TELEFONO)
                t$TELEFONO_MOVIL <- gsub("[^0-9]", "", t$TELEFONO_MOVIL)
        }
        if (fileNames[x] == "temp/02-Candidatos_Formacion_Parte1.csv") {
                t$TITULO <- as.character(t$TITULO)
                t$TITULO[t$TITULO==""] <- paste( as.character(t$NIVEL[t$TITULO==""]), as.character(t$AREA[t$TITULO==""]))
                t$TITULO <- as.factor(t$TITULO)
        }
        if (fileNames[x] == "temp/03-Candidatos_Formacion_Parte2.csv") {
                t$TITULO <- as.character(t$TITULO)
                t$TITULO[t$TITULO==""] <- paste( as.character(t$NIVEL[t$TITULO==""]), as.character(t$AREA[t$TITULO==""]))
                t$TITULO <- as.factor(t$TITULO)
        }
        fwrite(t,fileNamesOut[x], sep = "~")
        rm(t)
        gc()
}

Vamos a descargar las fotos los adjuntos y los informes y dejarlos en una carpeta llamada output/fotos output/adjuntos y output/informes

proc.time() - ptm
LS0tCnRpdGxlOiAnTGltcGllemEgeSBGaWx0cmFkbyBlLVByZXNlbGVjIHBhcmEgbGEgY2FyZ2EgZGUgSG9sZGluZyBlbiBDb3JuZXJzdG9uZSAnCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICB0aGVtZTogc3BhY2VsYWIKICAgIHRvYzogeWVzCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0Ci0tLQoKTGlicmVyw61hcwpgYGB7ciwgd2FybmluZz1GQUxTRX0KbGlicmFyeShkcGx5cikKbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShkb3dubG9hZGVyKQpgYGAKCkZ1bmNpb25lcwpgYGB7cn0KaXNWYWxpZEVtYWlsIDwtIGZ1bmN0aW9uKHgpIHsKICAgICAgICBncmVwbCgiXFw8W0EtWjAtOS5fJSstXStAW0EtWjAtOS4tXStcXC5bQS1aXXsyLH1cXD4iLCBhcy5jaGFyYWN0ZXIoeCksIGlnbm9yZS5jYXNlPVRSVUUpCn0KCmxlZXJBcmNoaXZvIDwtIGZ1bmN0aW9uKG5vbWJyZSkgewogICAgICAgIHRlbXAgPC0gZnJlYWQobm9tYnJlLCBzZXAgPSAifiIsIHF1b3RlID0gIiIsIGVuY29kaW5nID0gIlVURi04Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IFRSVUUsIGZpbGwgPSBUUlVFLCBkYXRhLnRhYmxlID0gVFJVRSkKICAgICAgICBjb2xuYW1lcyh0ZW1wKSA8LSB0b3VwcGVyKGNvbG5hbWVzKHRlbXApKQogICAgICAgIGlmIChhbnlEdXBsaWNhdGVkKGNvbG5hbWVzKHRlbXApKSkgewogICAgICAgICAgICAgY29sbmFtZXModGVtcCkgPC0gbWFrZS51bmlxdWUoY29sbmFtZXModGVtcCkgKSAgIAogICAgICAgIH0KICAgICAgICB0ZW1wCn0KYGBgCgoKCiNPYmpldGl2bwoKRWwgb2JqZXRpdm8gZGUgZXN0ZSBzY3JpcHQgZXMgZWwgZmlsdHJhZG8gZGUgbG9zIGRhdG9zIGRlIG9yaWdlbiBkZSBlLVByZXNlbGVjIEVTUCBwYXJhIHN1IGNhcmdhIGVuIENvcm5lcnN0b25lIHBhcmEgZWwgw6FtYml0byBIb2xkaW5nLgoKRW4gbGEgc3ViY2FycGV0YSBkYXRvc19vcmlnaW5hbGVzLyBlc3TDoW4gdG9kb3MgbG9zIGFyY2hpdm9zIGZ1ZW50ZSBvYnRlbmlkb3MgZGUgZS1QcmVzZWxlYyBFU1AKCmBgYHtyfQpwdG0gPC0gcHJvYy50aW1lKCkKZmlsZXMgPC0gbGlzdC5maWxlcyhwYXRoPSJkYXRvc19vcmlnaW5hbGVzIiwgcGF0dGVybj0iKi5jc3YiLCBmdWxsLm5hbWVzPVQsIHJlY3Vyc2l2ZT1GQUxTRSkKcHJpbnQgKGZpbGVzKQpybShmaWxlcykKYGBgCgoKRW4gbGEgc3ViY2FycGV0YSBvdXRwdXQvIGRlamFyZW1vcyBsb3MgZmljaGVyb3MgZGUgc2FsaWRhIHlhIGZpbHRyYWRvcyB5IGxpbXBpYWRvcwoKI01hZXN0cm8gZGUgQ2FuZGlkYXRvcwoKTGVlbW9zIGVsIGZpY2hlcm8gZGUgbWFlc3RybyBkZSBjYW5kaWRhdG9zIHkgdmFtb3MgYSBpciBlbGltaW5hbmRvIGNhbmRpZGF0b3MgcG9yIGRpZmVyZW50ZXMgY3JpdGVyaW9zCiogUHJpbWVybyBhbm9uaW1pemFkb3MsIG5vbWJyZSBlbiBibGFuY28sIGVtYWlsIGVuIGJsYW5jbywgZW1haWxzIGZpY3RpY2lvcwpgYGB7cn0Kbm9tYnJlIDwtICJkYXRvc19vcmlnaW5hbGVzLzAxLUNhbmRpZGF0b3NfRGF0b3NfUGVyc29uYWxlcy5jc3YiCmNhbmRpZGF0b3NfbWFlc3RybyA8LSBsZWVyQXJjaGl2byhub21icmUpCmNhbmRpZGF0b3NfbWFlc3RybyA8LSBmaWx0ZXIoY2FuZGlkYXRvc19tYWVzdHJvLCBOT01CUkUgIT0gIlhYWFhYWCIpCmNhbmRpZGF0b3NfbWFlc3RybyA8LSBmaWx0ZXIoY2FuZGlkYXRvc19tYWVzdHJvLCBOT01CUkUgIT0gIiIpCmNhbmRpZGF0b3NfbWFlc3RybyA8LSBmaWx0ZXIoY2FuZGlkYXRvc19tYWVzdHJvLCBFTUFJTCAhPSAiIikKY2FuZGlkYXRvc19tYWVzdHJvIDwtIGZpbHRlcihjYW5kaWRhdG9zX21hZXN0cm8sICEoRU1BSUwgJWxpa2UlICJAaW5mb2pvYnMiKSkKY2FuZGlkYXRvc19tYWVzdHJvIDwtIGZpbHRlcihjYW5kaWRhdG9zX21hZXN0cm8sICEoRU1BSUwgJWxpa2UlICJAaW5mb2VtcGxlbyIpKQpjYW5kaWRhdG9zX21hZXN0cm8gPC0gZmlsdGVyKGNhbmRpZGF0b3NfbWFlc3RybywgIShFTUFJTCAlbGlrZSUgIkB2YWNpby5jb20iKSkKY2FuZGlkYXRvc19tYWVzdHJvIDwtIGZpbHRlcihjYW5kaWRhdG9zX21hZXN0cm8sICEoRU1BSUwgJWxpa2UlICJAbmFkYSIpKQpgYGAKCgpEZSBsb3MgcXVlIHF1ZWRhbiwgdmFtb3MgYSBleHRyYWVyIGxvcyBJRCBkZSBjYW5kaWRhdG9zIGRlIGxvcyBhY3R1YWxpemFkb3MgZW4gbG9zIMO6bHRpbW9zIDI0IG1lc2VzCmBgYHtyfQpmZWNoYV9kZXNkZSA8LSB0b2RheSgpIC0gbW9udGhzKDI0KQpmZWNoYV9oYXN0YSA8LSB0b2RheSgpCmBgYApZIG5vcyBzYWxlbgpgYGB7cn0KY2FuZGlkYXRvc19oaXN0b3JpY29zIDwtIGNhbmRpZGF0b3NfbWFlc3RybyAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKGRteV9obXMoRkVDSEFfQUNUVUFMSVpBQ0lPTikgPj0gZmVjaGFfZGVzZGUpICU+JQogICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3QoSURfQ0FORElEQVRPKQpzcHJpbnRmKCJOwrogZGUgQ2FuZGlkYXRvczogJWkiLCBucm93KGNhbmRpZGF0b3NfaGlzdG9yaWNvcykpCmBgYAoKCkFudGVzIGRlIHNlZ3VpciBlbGltaW5hbmRvIGNhbmRpZGF0b3MgcG9yIG90cm9zIGNyaXRlcmlvcywgdGVuZW1vcyBxdWUgaWRlbnRpZmljYXIgY3XDoWxlcyBkZSBlbGxvcyBlc3TDoW4gZW4gcHJvY2Vzb3Mgdml2b3MsIG5vIHNlYSBxdWUgZWxpbWluZW1vcyBjYW5kaWRhdG9zIGFjdHVhbGl6YWRvcyBoYWNlIG11Y2hvIHBlcm8gcXVlIGVzdMOhbiBlbiB1biBwcm9jZXNvIHZpdm8KCiNNYWVzdHJvIGRlIFByb2Nlc29zCgpUZW5lbW9zIHF1ZSBsZWVyIGVsIGZpY2hlcm8gbWFlc3RybyBkZSBwcm9jZXNvcyBkZSBzZWxlY2Npw7NuIChxdWUgc29uIHRyZXM6IGVzcGVjaWFsaXN0YXMsIGJlY2FyaW9zIHkgbWFzaXZvcykgYWJpZXJ0b3MgcGFyYSBpZGVudGlmaWNhciBxdcOpIHByb2Nlc29zIGVzdMOhbiAidml2b3MiIgoKRXN0b3Mgc2Vyw6FuIGxvcyBjcml0ZXJpb3MgZGUgZmlsdHJhZG8gYSBkZWZpbmlyIHBvciBlbCB1c3VhcmlvIGFudGVzIGRlIGVqZWN1dGFyIGxhIGNhcmdhCmBgYHtyfQojIEVzdG9zIGZpbHRyb3Mgbm8gc29uIG5lY2VzYXJpb3Mgc2kgbWlncmFtb3MgdG9kYSBFc3Bhw7FhCiNnZXN0b3Jlc19maWx0cm8gPC0gYygiTmFpYXJhIE1hcnTDrW5leiIsICJFbGVuYSBIZXJib3NhIiwgIlNhcmEgQWJhZCIsICJTdXNhbmEgR3V0aWVycmV6IiwgIiIpCiMgbWFzaXZvc19maWx0cm8gPC0gYygiMTI4NDEiLCIxMjg0MiIpCiMgYmVjYXJpb3NfZmlsdHJvIDwtIGMoIk5haWFyYSBNYXJ0w61uZXoiLCAiRWxlbmEgSGVyYm9zYSIsICJTYXJhIEFiYWQiLCAiU3VzYW5hIEd1dGllcnJleiIsICIiKQpgYGAKCgoKYGBge3IsIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CiNQRU5ESUVOVEUgREVURVJNSU5BUiBTSSBVU0FNT1MgQUxHVU4gQ1JJVEVSSU8gTUFTIFBBUkEgREVGSU5JUiBRVUUgVU4gUFJPQ0VTTyBFU1TDiSBBQ1RJVk8Kbm9tYnJlIDwtICJkYXRvc19vcmlnaW5hbGVzLzE5LVByb2Nlc29zX01hc2l2b3MuY3N2IgptYXNpdm9zX21hZXN0cm8gPC0gbGVlckFyY2hpdm8obm9tYnJlKQoKbm9tYnJlIDwtICJkYXRvc19vcmlnaW5hbGVzLzIwLVByb2Nlc29zX0VzcGVjaWFsaXN0YXMuY3N2Igplc3BlY2lhbGlzdGFzX21hZXN0cm8gPC0gbGVlckFyY2hpdm8obm9tYnJlKQoKbm9tYnJlIDwtICJkYXRvc19vcmlnaW5hbGVzLzIxLVByb2Nlc29zX0JlY2FyaW9zLmNzdiIKYmVjYXJpb3NfbWFlc3RybyA8LSBsZWVyQXJjaGl2byhub21icmUpCgpub21icmUgPC0gImRhdG9zX29yaWdpbmFsZXMvMzctT2ZlcnRhcy5jc3YiCm9mZXJ0YXNfbWFlc3RybyA8LSBsZWVyQXJjaGl2byhub21icmUpCgoKcHJvY2Vzb3NfYWN0aXZvcyA8LSBlc3BlY2lhbGlzdGFzX21hZXN0cm8gJT4lCiAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcihFU1RBRE9fUFJPQ0VTTyA9PSAiQWJpZXJ0byIsIEdFU1RPUiAlaW4lIGdlc3RvcmVzX2ZpbHRybykgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgIHNlbGVjdChJRF9QUk9DRVNPKQoKcHJvY2Vzb3NfYWN0aXZvcyA8LSBtYXNpdm9zX21hZXN0cm8gJT4lCiAgICAgICAgZmlsdGVyKEFDVElWTyA9PSAiU8OtIikgJT4lCiAgICAgICAgc2VsZWN0KElEX1BST0NFU08pICU+JQogICAgICAgIGJpbmRfcm93cyhwcm9jZXNvc19hY3Rpdm9zKQoKcHJvY2Vzb3NfYWN0aXZvcyA8LSBiZWNhcmlvc19tYWVzdHJvICU+JQogICAgICAgIGZpbHRlcihFU1RBRE9fUFJPQ0VTTyA9PSAiQWJpZXJ0byIpICU+JQogICAgICAgIHNlbGVjdChJRF9QUk9DRVNPKSAlPiUKICAgICAgICBiaW5kX3Jvd3MocHJvY2Vzb3NfYWN0aXZvcykKCm9mZXJ0YXNfYWN0aXZhcyA8LSBvZmVydGFzX21hZXN0cm8gJT4lCiAgICAgICAgZmlsdGVyKGRteV9obXMoRkVDSEFfRklOKSA+IGRteSgiMDEtMDQtMjAxNyIpKSAlPiUKICAgICAgICBzZWxlY3QoUkVGRVJFTkNJQSkgCnJtKG1hc2l2b3NfbWFlc3RybykKcm0oZXNwZWNpYWxpc3Rhc19tYWVzdHJvKQpybShiZWNhcmlvc19tYWVzdHJvKQpybShvZmVydGFzX21hZXN0cm8pCmBgYAoKTm9zIHNhbGVuIGxvcyBzaWd1aWVudGVzIHByb2Nlc29zIGFjdGl2b3MsIGVudHJlIGJlY2FyaW9zLCBtYXNpdm9zIHkgZXNwZWNpYWxpc3RhcwpgYGB7ciwgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KcHJpbnQobnJvdyhwcm9jZXNvc19hY3Rpdm9zKSkKYGBgCgoKI0NhbmRpZGF0b3MgZW4gcHJvY2Vzb3MgVml2b3MKClRlbmVtb3MgcXVlIGV4dHJhZXIgbG9zIElEIGRlIGxvcyBjYW5kaWRhdG9zIGFzb2NpYWRvcyBhIGxvcyBwcm9jZXNvcyB2aXZvcywgaW5kZXBlbmRpZW50ZW1lbnRlIGRlbCBlc3RhZG8gZGVsIGNhbmRpZGF0bwpgYGB7ciwgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0Kbm9tYnJlIDwtICJkYXRvc19vcmlnaW5hbGVzLzMzLVByb2Nlc29zX0NhbmRpZGF0b3NfSW5zY3JpdG9zLmNzdiIKY2FuZGlkYXRvc19pbnNjcml0b3MgPC0gbGVlckFyY2hpdm8obm9tYnJlKQpjYW5kaWRhdG9zX2luc2NyaXRvcyA8LSB0YmxfZGYoY2FuZGlkYXRvc19pbnNjcml0b3MpCmNhbmRpZGF0b3NfcHJvY2Vzb3Nfdml2b3MgPC0gY2FuZGlkYXRvc19pbnNjcml0b3MgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcihJRF9QUk9DRVNPICVpbiUgcHJvY2Vzb3NfYWN0aXZvcyRJRF9QUk9DRVNPKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgc2VsZWN0KElEX0NBTkRJREFUTykKI1BvciBzaSBhY2FzbywgdmFtb3MgYSB2ZXIgc2kgZXhpc3RlbiBjYW5kaWRhdG9zIGFkaWNpb25hbGVzIGVuIGVzdG9zIG90cm9zIGZpY2hlcm9zCm5vbWJyZSA8LSAiZGF0b3Nfb3JpZ2luYWxlcy8zNS1Qcm9jZXNvc19Fc3BlY2lhbGlzdGFzX1Byb2dyZXNvLmNzdiIKdGVtcCA8LSBsZWVyQXJjaGl2byhub21icmUpCmNhbmRpZGF0b3NfcHJvY2Vzb3Nfdml2b3MgPC0gdGVtcCAlPiUKICAgICAgICBmaWx0ZXIoSURfUFJPQ0VTTyAlaW4lIHByb2Nlc29zX2FjdGl2b3MkSURfUFJPQ0VTTykgJT4lCiAgICAgICAgc2VsZWN0KElEX0NBTkRJREFUTykgJT4lCiAgICAgICAgYmluZF9yb3dzKGNhbmRpZGF0b3NfcHJvY2Vzb3Nfdml2b3MpICU+JQogICAgICAgIGRpc3RpbmN0KCkKCiNQb3Igc2kgYWNhc28sIHZhbW9zIGEgdmVyIHNpIGV4aXN0ZW4gY2FuZGlkYXRvcyBhZGljaW9uYWxlcyBlbiBlc3RvcyBvdHJvcyBmaWNoZXJvcwpub21icmUgPC0gImRhdG9zX29yaWdpbmFsZXMvMzQtUHJvY2Vzb3NfQmVjYXJpb3NfUHJvZ3Jlc28uY3N2Igp0ZW1wIDwtIGxlZXJBcmNoaXZvKG5vbWJyZSkKY2FuZGlkYXRvc19wcm9jZXNvc192aXZvcyA8LSB0ZW1wICU+JQogICAgICAgIGZpbHRlcihJRF9QUk9DRVNPICVpbiUgcHJvY2Vzb3NfYWN0aXZvcyRJRF9QUk9DRVNPKSAlPiUKICAgICAgICBzZWxlY3QoSURfQ0FORElEQVRPKSAlPiUKICAgICAgICBiaW5kX3Jvd3MoY2FuZGlkYXRvc19wcm9jZXNvc192aXZvcykgJT4lCiAgICAgICAgZGlzdGluY3QoKQoKI1BvciBzaSBhY2FzbywgdmFtb3MgYSB2ZXIgc2kgZXhpc3RlbiBjYW5kaWRhdG9zIGFkaWNpb25hbGVzIGVuIGVzdG9zIG90cm9zIGZpY2hlcm9zCm5vbWJyZSA8LSAiZGF0b3Nfb3JpZ2luYWxlcy8zNi1HZXN0aW9uX0JvbHNhX01hc2l2b3MuY3N2Igp0ZW1wIDwtIGxlZXJBcmNoaXZvKG5vbWJyZSkKY2FuZGlkYXRvc19wcm9jZXNvc192aXZvcyA8LSB0ZW1wICU+JQogICAgICAgIGZpbHRlcihJRF9QUk9DRVNPICVpbiUgcHJvY2Vzb3NfYWN0aXZvcyRJRF9QUk9DRVNPKSAlPiUKICAgICAgICBzZWxlY3QoSURfQ0FORElEQVRPKSAlPiUKICAgICAgICBiaW5kX3Jvd3MoY2FuZGlkYXRvc19wcm9jZXNvc192aXZvcykgJT4lCiAgICAgICAgZGlzdGluY3QoKQoKI1BvciBzaSBhY2FzbywgdmFtb3MgYSB2ZXIgc2kgZXhpc3RlbiBjYW5kaWRhdG9zIGFkaWNpb25hbGVzIGVuIGVzdG9zIG90cm9zIGZpY2hlcm9zCm5vbWJyZSA8LSAiZGF0b3Nfb3JpZ2luYWxlcy80MS1DYW5kaWRhdG9zX0VuX09mZXJ0YS5jc3YiCnRlbXAgPC0gbGVlckFyY2hpdm8obm9tYnJlKQpjYW5kaWRhdG9zX3Byb2Nlc29zX3Zpdm9zIDwtIHRlbXAgJT4lCiAgICAgICAgZmlsdGVyKElEX09GRVJUQSAlaW4lIG9mZXJ0YXNfYWN0aXZhcyRSRUZFUkVOQ0lBKSAlPiUKICAgICAgICBzZWxlY3QoSURfQ0FORElEQVRPKSAlPiUKICAgICAgICBiaW5kX3Jvd3MoY2FuZGlkYXRvc19wcm9jZXNvc192aXZvcykgJT4lCiAgICAgICAgZGlzdGluY3QoKQpybShjYW5kaWRhdG9zX2luc2NyaXRvcykKYGBgCgpOb3Mgc2FsZW4KYGBge3IsIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CnByaW50KG5yb3coY2FuZGlkYXRvc19wcm9jZXNvc192aXZvcykpCmBgYAoKCgpKdW50YW5kbyBsb3MgY2FuZGlkYXRvcyBlbiBwcm9jZXNvcyB2aXZvcywgeSBsb3MgY2FuZGlkYXRvcyBhY3R1YWxpemFkb3MgcmVjaWVudGVtZW50ZSwgdGVuZW1vcyBlbCBzaWd1aWVudGUgbsO6bWVybyBkZSBjYW5kaWRhdG9zCmBgYHtyLCBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQpjYW5kaWRhdG9zX2FfbWlncmFyIDwtIGJpbmRfcm93cyhjYW5kaWRhdG9zX3Byb2Nlc29zX3Zpdm9zLGNhbmRpZGF0b3NfaGlzdG9yaWNvcykgJT4lIGRpc3RpbmN0KCkKcHJpbnQobnJvdyhjYW5kaWRhdG9zX2FfbWlncmFyKSkKYGBgCgpWYW1vcyBhIGVsaW1pbmFyIHBvciB0YW50byB0b2RvcyBsb3MgY2FuZGlkYXRvcyBxdWUgbm8gZXN0w6luIGVuIGVzYSBsaXN0YSB5IGEgdmVyIGN1w6FudG9zIHF1ZWRhbgpgYGB7ciwgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KY2FuZGlkYXRvc19tYWVzdHJvIDwtIGZpbHRlcihjYW5kaWRhdG9zX21hZXN0cm8sIElEX0NBTkRJREFUTyAlaW4lIGNhbmRpZGF0b3NfYV9taWdyYXIkSURfQ0FORElEQVRPKQpwcmludChucm93KGNhbmRpZGF0b3NfbWFlc3RybykpCnJtKGNhbmRpZGF0b3NfYV9taWdyYXIpCmBgYAoKU2kgbm8gY29pbmNpZGVuIGxvcyBuw7ptZXJvcyBlcyBwb3JxdWUgaGF5IGNhbmRpZGF0b3MgcXVlIGhlbW9zIGVsaW1pbmFkbyBwcmV2aWFtZW50ZSBwb3IgYWxndW5vIGRlIGxvcyBvdHJvcyBjcml0ZXJpb3MuIAoKCgoKI0ZpbHRyb3MgQWRpY2lvbmFsZXMgZW4gQ2FuZGlkYXRvcwoKClRhbXBvY28gY2FyZ2Ftb3MgY2FuZGlkYXRvcyBjdXlvIENWIG5vIGVzdMOhIGFjdGl2bwpgYGB7cn0KY2FuZGlkYXRvc19tYWVzdHJvIDwtIGZpbHRlcihjYW5kaWRhdG9zX21hZXN0cm8sIENWX0FDVElWTyA9PSAiU2kiKQpgYGAKYGBge3J9CnByaW50KG5yb3coY2FuZGlkYXRvc19tYWVzdHJvKSkKYGBgCgoKdHJhdGFtb3MgZGUgYXJyZWdsYXIgZW1haWxzIGludsOhbGlkb3M/CmBgYHtyfQplbWFpbHNfaW5jb3JlY3RvcyA8LSBmaWx0ZXIoY2FuZGlkYXRvc19tYWVzdHJvLCFpc1ZhbGlkRW1haWwoRU1BSUwpKQpwcmludChlbWFpbHNfaW5jb3JlY3Rvc1siRU1BSUwiXSkKCmBgYAoKCmVsaW1pbmFtb3MgbG9zIGVtYWlscyBpbnbDoWxpZG9zIApgYGB7cn0KY2FuZGlkYXRvc19tYWVzdHJvIDwtIGZpbHRlcihjYW5kaWRhdG9zX21hZXN0cm8sIGlzVmFsaWRFbWFpbChFTUFJTCkpCmBgYAoKCgpEZSBsb3MgY2FuZGlkYXRvcyBkdXBsaWNhZG9zIHRyYXRhcmVtb3MgZGUgY2FyZ2FyIMO6bmljYW1lbnRlIHVuYSBvY3VycmVuY2lhLiBFc3RvcyBzb24gYWxndW5vcyBkZSBsb3MgcmVwZXRpZG9zCmBgYHtyfQplbWFpbHNfcmVwZXRpZG9zIDwtIGNhbmRpZGF0b3NfbWFlc3RybyAlPiUgZ3JvdXBfYnkoRU1BSUwpICU+JQogICAgICAgIHN1bW1hcmlzZShjb3VudCA9IG4oKSkgJT4lIAogICAgICAgIGFycmFuZ2UoZGVzYyhjb3VudCkpICU+JSBmaWx0ZXIoY291bnQgPiAxKQpwcmludChlbWFpbHNfcmVwZXRpZG9zKQpgYGAKCkVsaW1pbmFtb3MgY2FuZGlkYXRvcyBkdXBsaWNhZG9zLCBkZWphbmRvIMO6bmljYW1lbnRlIGR1cGxpY2Fkb3Mgc2kgZXN0w6FuIGFzb2NpYWRvcyBhIHByb2Nlc29zCmBgYHtyfQojT2J0ZW5lbW9zIHVuYSBsaXN0YSBkZSBlbWFpbHMgeSBjYW5kaWRhdG9zIHF1ZSBubyBlc3TDoW4gZW4gcHJvY2Vzb3Mgdml2b3MKY2FuZGlkYXRvc19hX3VuaWZpY2FyIDwtIGNhbmRpZGF0b3NfbWFlc3RybyAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIoRU1BSUwgJWluJSBlbWFpbHNfcmVwZXRpZG9zJEVNQUlMKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3QoSURfQ0FORElEQVRPKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIoIShJRF9DQU5ESURBVE8gJWluJSBjYW5kaWRhdG9zX3Byb2Nlc29zX3Zpdm9zJElEX0NBTkRJREFUTykpCmNhbmRpZGF0b3NfbWFudGVuZXIgPC0gY2FuZGlkYXRvc19tYWVzdHJvICU+JQogICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIoSURfQ0FORElEQVRPICVpbiUgY2FuZGlkYXRvc19hX3VuaWZpY2FyJElEX0NBTkRJREFUTykgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgIGFycmFuZ2UoZGVzYyhkbXlfaG1zKEZFQ0hBX0FDVFVBTElaQUNJT04pKSkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgIGRpc3RpbmN0KEVNQUlMLCAua2VlcF9hbGw9VFJVRSApICU+JQogICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3QoSURfQ0FORElEQVRPKQpjYW5kaWRhdG9zX2VsaW1pbmFyIDwtIGNhbmRpZGF0b3NfbWFlc3RybyAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKElEX0NBTkRJREFUTyAlaW4lIGNhbmRpZGF0b3NfYV91bmlmaWNhciRJRF9DQU5ESURBVE8pICU+JQogICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIoIShJRF9DQU5ESURBVE8gJWluJSBjYW5kaWRhdG9zX21hbnRlbmVyJElEX0NBTkRJREFUTykpICU+JQogICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3QoSURfQ0FORElEQVRPKQoKI0Fob3JhIGVsaW1pbmFtb3MgZXN0b3MgY2FuZGlkYXRvcyBkZWwgbWFlc3RybwpjYW5kaWRhdG9zX21hZXN0cm8gPC0gZmlsdGVyKGNhbmRpZGF0b3NfbWFlc3RybywgIShJRF9DQU5ESURBVE8gJWluJSBjYW5kaWRhdG9zX2VsaW1pbmFyJElEX0NBTkRJREFUTykpCnJtKGNhbmRpZGF0b3NfYV91bmlmaWNhcikKcm0oY2FuZGlkYXRvc19tYW50ZW5lcikKcm0oY2FuZGlkYXRvc19lbGltaW5hcikKYGBgCgoKQ29tcHJvYmFtb3Mgc2kgcXVlZGFuIGR1cGxpY2Fkb3MKYGBge3J9CmVtYWlsc19yZXBldGlkb3MgPC0gY2FuZGlkYXRvc19tYWVzdHJvICU+JSBncm91cF9ieShFTUFJTCkgJT4lCiAgICAgICAgc3VtbWFyaXNlKGNvdW50ID0gbigpKSAlPiUgCiAgICAgICAgYXJyYW5nZShkZXNjKGNvdW50KSkgJT4lIGZpbHRlcihjb3VudCA+IDEpCnByaW50KGVtYWlsc19yZXBldGlkb3MpCmBgYAoKU2kgcXVlZGFuIGR1cGxpY2Fkb3MgcHVlZGUgc2VyIHBvciBkb3MgbW90aXZvczoKMS4gRWwgY2FuZGlkYXRvIGVzdMOhIGluc2NyaXRvIGEgbcOhcyBkZSB1bmEgb2ZlcnRhIGRlIGVtcGxlbyBjb24gZWwgbWlzbW8gZW1haWwgcGVybyBkaWZlcmVudGUgSUQgZGUgY2FuZGlkYXRvCjIuIEVsIGNhbmRpZGF0byBlc3TDoSBpbnNjcml0byBhIHVuYSBvZmVydGEgZGUgZW1wbGVvIChvIG3DoXMpIGNvbiB1biBlbWFpbCBlIElEIGRlIENhbmRpZGF0byB5IGFwYXJlY2UgZW4gZWwgaGlzdMOzcmljbyBjb24gZWwgbWlzbW8gZW1haWwgcGVybyBvdHJvIElEIGRlIENhbmRpZGF0by4KCkVuIGVzdGUgY2FzbyBlbGltaW5hbW9zIGxvcyBjYW5kaWRhdG9zIHF1ZSBjdW1wbGVuIGxhIGNhc3XDrXN0aWNhIG7Dum1lcm8gMiwgZGVqYW5kbyDDum5pY2FtZW50ZSBzdSBJRCBkZSBjYW5kaWRhdG8gcXVlIGVzdMOhIGFzb2NpYWRvIGEgYWxndW5hIG9mZXJ0YSBkZSBlbXBsZW8uCgpgYGB7cn0KI09idGVuZW1vcyB1bmEgbGlzdGEgZGUgZW1haWxzIHkgY2FuZGlkYXRvcyBxdWUgbm8gZXN0w6FuIGVuIHByb2Nlc29zIHZpdm9zCmNhbmRpZGF0b3NfYV91bmlmaWNhciA8LSBjYW5kaWRhdG9zX21hZXN0cm8gJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKEVNQUlMICVpbiUgZW1haWxzX3JlcGV0aWRvcyRFTUFJTCkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VsZWN0KElEX0NBTkRJREFUTykgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyKCEoSURfQ0FORElEQVRPICVpbiUgY2FuZGlkYXRvc19wcm9jZXNvc192aXZvcyRJRF9DQU5ESURBVE8pKQojeSBub3MgbG9zIGNhcmdhbW9zCmNhbmRpZGF0b3NfbWFlc3RybyA8LSBmaWx0ZXIoY2FuZGlkYXRvc19tYWVzdHJvLCAhKElEX0NBTkRJREFUTyAlaW4lIGNhbmRpZGF0b3NfYV91bmlmaWNhciRJRF9DQU5ESURBVE8pKQpybShjYW5kaWRhdG9zX2FfdW5pZmljYXIpCmBgYAoKCkNvbXByb2JhbW9zIHNpIHF1ZWRhbiBkdXBsaWNhZG9zCmBgYHtyfQplbWFpbHNfcmVwZXRpZG9zIDwtIGNhbmRpZGF0b3NfbWFlc3RybyAlPiUgZ3JvdXBfYnkoRU1BSUwpICU+JQogICAgICAgIHN1bW1hcmlzZShjb3VudCA9IG4oKSkgJT4lIAogICAgICAgIGFycmFuZ2UoZGVzYyhjb3VudCkpICU+JSBmaWx0ZXIoY291bnQgPiAxKQpwcmludChlbWFpbHNfcmVwZXRpZG9zKQpybShlbWFpbHNfcmVwZXRpZG9zKQpgYGAKCgpTaSBlbiBlc3RlIHB1bnRvIHF1ZWRhbiBlbWFpbHMgcmVwZXRpZG9zLCB0ZW5lbW9zIHVuIHByb2JsZW1hIHlhIHF1ZSBzZSB0cmF0YSBkZSBwZXJzb25hcyBxdWUgc2UgaGFuIGFwdW50YWRvIGEgZG9zIHByb2Nlc29zIHZpZ2VudGVzLCBjb24gZG9zIElEcyBkZSBjYW5kaWRhdG8gZGlzdGludG8gcGVybyBpZMOpbnRpY28gZW1haWwuIEVuIENvcm5lcnN0b25lIHPDs2xvIHNlIHB1ZWRlIGNhcmdhciB1bm8gZGUgbG9zIGRvcyBwdWVzdG8gcXVlIGxhIGNsYXZlIHByaW5jaXBhbCBlcyBlbCBlbWFpbCwgZWwgdXN1YXJpbyBkZSBhY2Nlc28gYSBDb3JuZXJzdG9uZSBzZXLDoSBlbCBlbWFpbCB5IGFkZW3DoXMgc2Vyw6EgdW5vIGRlIGxvcyBtZWRpb3MgZGUgY29udGFjdG8gY29uIGVsIGNhbmRpZGF0byBwb3IgbG8gcXVlIG5vIGxvIHBvZGVtb3MgY2FtYmlhci4gCgoKUEVOU0FSIFFVRSBIQUNFUiAKCgoKCgoKQSB2ZXIgY3XDoW50b3MgY2FuZGlkYXRvcyBub3MgcXVlZGFuOgpgYGB7cn0KZmVjaGFzIDwtIHNlbGVjdChjYW5kaWRhdG9zX21hZXN0cm8sSURfQ0FORElEQVRPLEZFQ0hBX0FDVFVBTElaQUNJT04pCmZlY2hhcyRGRUNIQV9BQ1RVQUxJWkFDSU9OIDwtIGFzLlBPU0lYY3QoZGF0ZShkbXlfaG1zKGZlY2hhcyRGRUNIQV9BQ1RVQUxJWkFDSU9OKSkpCmdncGxvdChmZWNoYXMsIGFlcyhGRUNIQV9BQ1RVQUxJWkFDSU9OKSkgKyAKICAgICAgICBnZW9tX2hpc3RvZ3JhbShhZXMoZmlsbD0uLmNvdW50Li4pKSArCiAgICAgICAgbGFicyh0aXRsZT0iSGlzdG9ncmFtYSBkZSBDYW5kaWRhdG9zIHBvciBmZWNoYSBkZSDDumx0aW1hIGFjdHVhbGl6YWNpw7NuIikgKwogICAgICAgIGxhYnMoeD0iRmVjaGEiLCB5PSJOw7ptZXJvIGRlIEFjdHVhbGl6YWNpb25lcyIpICsgCiAgICAgICAgc2NhbGVfeF9kYXRldGltZShicmVha3MgPSBkYXRlX2JyZWFrcygiMTIgbW9udGhzIiksCiAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBkYXRlX2Zvcm1hdCgiJVktJWIiKQogICAgICAgICAgICAgICAgICAgICAgICAgICkKZmVjaGFzIDwtIHNlbGVjdChjYW5kaWRhdG9zX21hZXN0cm8sSURfQ0FORElEQVRPLEZFQ0hBX0FMVEEpCmZlY2hhcyRGRUNIQV9BTFRBIDwtIGFzLlBPU0lYY3QoZGF0ZShkbXlfaG1zKGZlY2hhcyRGRUNIQV9BTFRBKSkpCmdncGxvdChmZWNoYXMsIGFlcyhGRUNIQV9BTFRBKSkgKyAKICAgICAgICBnZW9tX2hpc3RvZ3JhbShhZXMoZmlsbD0uLmNvdW50Li4pKSArCiAgICAgICAgbGFicyh0aXRsZT0iSGlzdG9ncmFtYSBkZSBDYW5kaWRhdG9zIHBvciBmZWNoYSBkZSBhbHRhIikgKwogICAgICAgIGxhYnMoeD0iRmVjaGEiLCB5PSJOw7ptZXJvIGRlIEFsdGFzIikgKyAKICAgICAgICBzY2FsZV94X2RhdGV0aW1lKGJyZWFrcyA9IGRhdGVfYnJlYWtzKCIyNCBtb250aHMiKSwKICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGRhdGVfZm9ybWF0KCIlWS0lYiIpCiAgICAgICAgKQpgYGAKCgoKCiMgRmlsdHJhZG8gZGVsIHJlc3RvIGRlIGZpY2hlcm9zCgpBaG9yYSB0ZW5lbW9zIHBvciB1biBsYWRvIGVsIGxpc3RhZG8gZGUgY2FuZGlkYXRvcyBhIG1pZ3JhciB5IHBvciBvdHJvIGVsIGxpc3RhZG8gZGUgcHJvY2Vzb3MgKHkgb2ZlcnRhcykgYSBtaWdyYXIuCkFob3JhIGhheSBxdWUgcHJvY2VzYXIgdG9kb3MgeSBjYWRhIHVubyBkZSBsb3MgZmljaGVyb3MgQ1NWIGRlIGVudHJhZGEgeSBmaWx0cmFybG9zIHBhcmEgZGVqYXIgw7puaWNhbWVudGUgbG9zIGNhbmRpZGF0b3MsIHByb2Nlc29zLCBvIGxhIGNvbWJpbmFjacOzbiBkZSDDoW1ib3MuCgoKYGBge3J9CiMgUG9yIGVsIG1vbWVudG8sIGVuIGxvcyBmaWNoZXJvcyBkZSBwcm9jZXNvcyBkZWphbW9zIHNvbG8gbGEgaW5mb3JtYWNpw7NuIGRlIGxvcyBwcm9jZXNvcyB2aXZvcywgZWxpbWluYW5kbyB0b2RvIGxvIGRlbcOhcwojIE90cmEgb3BjacOzbiBzZXLDrWEgZGVqYXIgbGEgaW5mb3JtYWNpw7NuIGRlIGxvcyBjYW5kaWRhdG9zIGhpc3TDs3JpY29zIHRhbWJpw6luIHBlcm8gbW9kaWZpY2FuZG8gZWwgSUQgZGUgcHJvY2VzbyBkZSB0b2RvcyBlbGxvcyBhIHVubyAiZmljdGljaW8iIGEgbWFwZWFyIGNvbiBlbCBEdW1teSBkZSBjb3JuZXJzdG9uZS4gQ29tbyBlbCByZXF1ZXJpbWllbnRvIGVzIGNhcmdhciBjYW5kaWRhdG9zIGhpc3TDs3JpY29zIMO6bmljYW1lbnRlLCAobm8gbGEgaW5mb3JtYWNpw7NuIGFzb2NpYWRhIGEgc3VzIDEgbyB2YXJpb3MgcHJvY2Vzb3MgZGUgc2VsZWNjacOzbiBoaXN0w7NyaWNvcykgZGUgbW9tZW50byBsbyBkZWphbW9zIGFzw60uCgpmaWxlTmFtZXMgPC0gbGlzdC5maWxlcyhwYXRoID0gImRhdG9zX29yaWdpbmFsZXMiLCBwYXR0ZXJuPSIqLmNzdiIsIGZ1bGwubmFtZXM9VCwgcmVjdXJzaXZlPUZBTFNFKQpmaWxlTmFtZXNPdXQgPC0gZmlsZS5wYXRoKCJ0ZW1wIixsaXN0LmZpbGVzKHBhdGggPSAiZGF0b3Nfb3JpZ2luYWxlcyIsIHBhdHRlcm49IiouY3N2IiwgcmVjdXJzaXZlPUZBTFNFKSkKZm9yKHggaW4gMTpsZW5ndGgoZmlsZU5hbWVzKSkgewogICAgICAgIHQ8LWxlZXJBcmNoaXZvKGZpbGVOYW1lc1t4XSkKICAgICAgICBpZiAoIklEX1BST0NFU08iICVpbiUgY29sbmFtZXModCkpIHsKICAgICAgICAgICAgICAgIHQgPC0gdCAlPiUgZmlsdGVyKElEX1BST0NFU08gJWluJSBwcm9jZXNvc19hY3Rpdm9zJElEX1BST0NFU08pCiAgICAgICAgfQogICAgICAgIGVsc2UgewogICAgICAgICAgICAgICAgaWYgKCJJRF9DQU5ESURBVE8iICVpbiUgY29sbmFtZXModCkpIHsKICAgICAgICAgICAgICAgICAgICAgICAgdCA8LSB0ICU+JSBmaWx0ZXIoSURfQ0FORElEQVRPICVpbiUgY2FuZGlkYXRvc19tYWVzdHJvJElEX0NBTkRJREFUTykKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIGVsc2UgewogICAgICAgICAgICAgICAgICAgICAgICBpZiAoIlJFRkVSRU5DSUEiICVpbiUgY29sbmFtZXModCkpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0IDwtIHQgJT4lIGZpbHRlcihSRUZFUkVOQ0lBICVpbiUgb2ZlcnRhc19hY3RpdmFzJFJFRkVSRU5DSUEpCiAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgZWxzZSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdCA8LSB0ICU+JSBmaWx0ZXIoIlJFRkVSRU5DSUEgT0ZFUlRBIiAlaW4lIG9mZXJ0YXNfYWN0aXZhcyRSRUZFUkVOQ0lBKQogICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICB9CiAgICAgICAgfQogICAgICAgIHByaW50KGZpbGVOYW1lc1t4XSkKICAgICAgICBwcmludChucm93KHQpKQogICAgICAgIGZ3cml0ZSh0LGZpbGVOYW1lc091dFt4XSwgc2VwID0gIn4iKQogICAgICAgIHJtKHQpCiAgICAgICAgZ2MoKQp9CmBgYAoKCiMgTGltcGllemEgYWRpY2lvbmFsCgpDb24gbG9zIGZpY2hlcm9zIHlhIGZpbHRyYWRvcywgbG9zIHZhbW9zIHByb2Nlc2FuZG8gdW5vIGEgdW5vIHkgcmVhbGl6YW5kbyBsaW1waWV6YXMgYWRpY2lvbmFsZXMKYGBge3J9CmZpbGVOYW1lcyA8LSBsaXN0LmZpbGVzKHBhdGggPSAidGVtcCIsIHBhdHRlcm49IiouY3N2IiwgZnVsbC5uYW1lcz1ULCByZWN1cnNpdmU9RkFMU0UpCmZpbGVOYW1lc091dCA8LSBmaWxlLnBhdGgoIm91dHB1dCIsbGlzdC5maWxlcyhwYXRoID0gInRlbXAiLCBwYXR0ZXJuPSIqLmNzdiIsIHJlY3Vyc2l2ZT1GQUxTRSkpCmZvcih4IGluIDE6bGVuZ3RoKGZpbGVOYW1lcykpIHsKICAgICAgICB0PC1sZWVyQXJjaGl2byhmaWxlTmFtZXNbeF0pCiAgICAgICAgaWYgKGZpbGVOYW1lc1t4XSA9PSAidGVtcC8wMS1DYW5kaWRhdG9zX0RhdG9zX1BlcnNvbmFsZXMuY3N2IikgewogICAgICAgICAgICAgICAgIyBDb252ZXJ0aXIgdGVsw6lmb25vcyBhIGZvcm1hdG8gbnVtw6lyaWNvCiAgICAgICAgICAgICAgICB0JFRFTEVGT05PIDwtIGdzdWIoIlteMC05XSIsICIiLCB0JFRFTEVGT05PKQogICAgICAgICAgICAgICAgdCRURUxFRk9OT19NT1ZJTCA8LSBnc3ViKCJbXjAtOV0iLCAiIiwgdCRURUxFRk9OT19NT1ZJTCkKICAgICAgICB9CiAgICAgICAgaWYgKGZpbGVOYW1lc1t4XSA9PSAidGVtcC8wMi1DYW5kaWRhdG9zX0Zvcm1hY2lvbl9QYXJ0ZTEuY3N2IikgewogICAgICAgICAgICAgICAgIyBTaSBlbCB0w610dWxvIGVzdMOhIHZhY8OtbyBjb25jYXRlbmFyIE5JVkVMIHkgQVJFQQogICAgICAgICAgICAgICAgdCRUSVRVTE8gPC0gYXMuY2hhcmFjdGVyKHQkVElUVUxPKQogICAgICAgICAgICAgICAgdCRUSVRVTE9bdCRUSVRVTE89PSIiXSA8LSBwYXN0ZSggYXMuY2hhcmFjdGVyKHQkTklWRUxbdCRUSVRVTE89PSIiXSksIGFzLmNoYXJhY3Rlcih0JEFSRUFbdCRUSVRVTE89PSIiXSkpCiAgICAgICAgICAgICAgICB0JFRJVFVMTyA8LSBhcy5mYWN0b3IodCRUSVRVTE8pCiAgICAgICAgfQogICAgICAgIGlmIChmaWxlTmFtZXNbeF0gPT0gInRlbXAvMDMtQ2FuZGlkYXRvc19Gb3JtYWNpb25fUGFydGUyLmNzdiIpIHsKICAgICAgICAgICAgICAgICMgU2kgZWwgdMOtdHVsbyBlc3TDoSB2YWPDrW8gY29uY2F0ZW5hciBOSVZFTCB5IEFSRUEKICAgICAgICAgICAgICAgIHQkVElUVUxPIDwtIGFzLmNoYXJhY3Rlcih0JFRJVFVMTykKICAgICAgICAgICAgICAgIHQkVElUVUxPW3QkVElUVUxPPT0iIl0gPC0gcGFzdGUoIGFzLmNoYXJhY3Rlcih0JE5JVkVMW3QkVElUVUxPPT0iIl0pLCBhcy5jaGFyYWN0ZXIodCRBUkVBW3QkVElUVUxPPT0iIl0pKQogICAgICAgICAgICAgICAgdCRUSVRVTE8gPC0gYXMuZmFjdG9yKHQkVElUVUxPKQogICAgICAgIH0KICAgICAgICBpZiAoZmlsZU5hbWVzW3hdID09ICJ0ZW1wLzE3LUNhbmRpZGF0b3NfT3Ryb3NfRGF0b3NfRG9jdW1lbnRvcy5jc3YiKSB7CiAgICAgICAgICAgICAgICAjIERlamFyIMO6bmljYW1lbnRlIGVsIG5vbWJyZSBkZWwgYXJjaGl2byBlbiBsdWdhciBkZSB0b2RhIGxhIFVSTAogICAgICAgICAgICAgICAgI2FudGVzLCBzYWNhciBsYXMgVVJMcyBhIHVuIGFyY2hpdm8gcGFyYSBkZXNjYXJnYXJsYXMgZGVzcHXDqXMKICAgICAgICAgICAgICAgIHQgJT4lIHNlbGVjdChET0NVTUVOVE8pICU+JSBmd3JpdGUoIm91dHB1dC9hZGp1bnRvcy9kZXNjYXJnYXJfY3ZzLmNzdiIpCiAgICAgICAgICAgICAgICB0JERPQ1VNRU5UTyA8LSBhcy5jaGFyYWN0ZXIodCRET0NVTUVOVE8pCiAgICAgICAgICAgICAgICB0JERPQ1VNRU5UTyA8LSBzdWJzdHIodCRET0NVTUVOVE8sIG5jaGFyKCJodHRwOi8vY2RuLmVwcmVzZWxlYy5jb20vYmJ2YS9Eb2N1bWVudG9zLyIpKzEsIG5jaGFyKHQkRE9DVU1FTlRPKSkKICAgICAgICAgICAgICAgIHQkRE9DVU1FTlRPIDwtIGFzLmZhY3Rvcih0JERPQ1VNRU5UTykKICAgICAgICB9CiAgICAgICAgaWYgKGZpbGVOYW1lc1t4XSA9PSAidGVtcC81NS1DYW5kaWRhdG9zX0RvY3VtZW50b3NfQmFjayIpIHsKICAgICAgICAgICAgICAgICMgRGVqYXIgw7puaWNhbWVudGUgZWwgbm9tYnJlIGRlbCBhcmNoaXZvIGVuIGx1Z2FyIGRlIHRvZGEgbGEgVVJMCiAgICAgICAgICAgICAgICAjYW50ZXMsIHNhY2FyIGxhcyBVUkxzIGEgdW4gYXJjaGl2byBwYXJhIGRlc2NhcmdhcmxhcyBkZXNwdcOpcwogICAgICAgICAgICAgICAgdCAlPiUgc2VsZWN0KERPQ1VNRU5UTykgJT4lIGZ3cml0ZSgib3V0cHV0L2FkanVudG9zL2Rlc2Nhcmdhcl9pbmZvcm1lcy5jc3YiKQogICAgICAgICAgICAgICAgdCRET0NVTUVOVE8gPC0gYXMuY2hhcmFjdGVyKHQkRE9DVU1FTlRPKQogICAgICAgICAgICAgICAgdCRET0NVTUVOVE8gPC0gc3Vic3RyKHQkRE9DVU1FTlRPLCBuY2hhcigiaHR0cDovL2Nkbi5lcHJlc2VsZWMuY29tL2JidmEvRG9jdW1lbnRvcy8iKSsxLCBuY2hhcih0JERPQ1VNRU5UTykpCiAgICAgICAgICAgICAgICB0JERPQ1VNRU5UTyA8LSBhcy5mYWN0b3IodCRET0NVTUVOVE8pCiAgICAgICAgfQogICAgICAgIDU1LUNhbmRpZGF0b3NfRG9jdW1lbnRvc19CYWNrCiAgICAgICAgZndyaXRlKHQsZmlsZU5hbWVzT3V0W3hdLCBzZXAgPSAifiIpCiAgICAgICAgcm0odCkKICAgICAgICBnYygpCn0KYGBgCgoKCgoKClZhbW9zIGEgZGVzY2FyZ2FyIGxhcyBmb3RvcyBsb3MgYWRqdW50b3MgeSBsb3MgaW5mb3JtZXMgeSBkZWphcmxvcyBlbiB1bmEgY2FycGV0YSBsbGFtYWRhIG91dHB1dC9mb3RvcyAgb3V0cHV0L2FkanVudG9zIHkgb3V0cHV0L2luZm9ybWVzCmBgYHtyLCBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQpmb3RvcyA8LSBhcy5jaGFyYWN0ZXIoY2FuZGlkYXRvc19tYWVzdHJvJEZPVE9HUkFGSUEpCmZpY2hlcm9zX2ZvdG9zIDwtIHN1YnN0cihmb3RvcywgbmNoYXIoImh0dHA6Ly9jZG4uZXByZXNlbGVjLmNvbS9iYnZhL0ZvdG9zLyIpKzEsIG5jaGFyKGZvdG9zKSkKZmljaGVyb3NfZm90b3Nfb3V0IDwtIHBhc3RlKCJvdXRwdXQvZm90b3MvIixmaWNoZXJvc19mb3RvcyxzZXA9IiIpCmZvciAoaSBpbiAxOmxlbmd0aChmaWNoZXJvc19mb3RvcykpIHsKICAgICAgICBpZiAoZm90b3NbaV0gIT0gIiIpIHsKICAgICAgICAgICAgICAgIHRyeShkb3dubG9hZChmb3Rvc1tpXSxkZXN0ZmlsZT1maWNoZXJvc19mb3Rvc19vdXRbaV0pKQogICAgICAgIH0KfQoKYGBgCgpgYGB7ciwgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KCm5vbWJyZSA8LSAidGVtcC8xNy1DYW5kaWRhdG9zX090cm9zX0RhdG9zX0RvY3VtZW50b3MuY3N2IgpjdnMgPC0gbGVlckFyY2hpdm8obm9tYnJlKSAKZmljaGVyb3NfZG9jcyA8LSBzdWJzdHIoZG9jdW1lbnRvcywgbmNoYXIoImh0dHA6Ly9jZG4uZXByZXNlbGVjLmNvbS9iYnZhL0RvY3VtZW50b3MvIikrMSwgbmNoYXIoZG9jdW1lbnRvcykpCmZpY2hlcm9zX2RvY3Nfb3V0IDwtIHBhc3RlKCJvdXRwdXQvYWRqdW50b3MvIixmaWNoZXJvc19kb2NzLHNlcD0iIikKZm9yIChpIGluIDE6bGVuZ3RoKGZpY2hlcm9zX2RvY3MpKSB7CiAgICAgICAgaWYgKGRvY3VtZW50b3NbaV0gIT0gIiIpIHsKICAgICAgICAgICAgICAgIHRyeShkb3dubG9hZChkb2N1bWVudG9zW2ldLGRlc3RmaWxlPWZpY2hlcm9zX2RvY3Nfb3V0W2ldKSkKICAgICAgICB9Cn0KYGBgCgpgYGB7cn0KcHJvYy50aW1lKCkgLSBwdG0KYGBgCgo=